package org.reborn.engine.model.mesh

import org.reborn.engine.enums.FaceMode._
import org.reborn.engine.enums.ArrayType._

import org.reborn.utils.SquareHeightMap

import org.reborn.math._
import MatrixF._
import VectorF._

object MeshShapes {
	
	/**
	 * Creates a sphere with vertices, normals, and 2D texture coordinates
	 * @param stacks the number of vertical "stacks" in the sphere or latitude lines
	 * @param slices the number of horizontal "slices" in the sphere or longitude lines
	 * @param radius the radius of the sphere
	 * @return A MutableMesh approximating a sphere. 
	 */
	def Sphere(stacks:Int, slices:Int, radius:Float):MutableMesh = {
		val faceCount = stacks * slices * 2
		val vertCount = (stacks + 1) * (slices + 1)
		
		val vertexData = new Array[Float](vertCount * 3);
		val texCoordData = new Array[Float](vertCount * 2);
		val normalData = new Array[Float](vertCount * 3);
		val faceData = new Array[Int](faceCount * 3)
		
		val twoPi = 2.0 * math.Pi;
		
		for(stack <- 0 until (stacks + 1)){
			val proportionStack = stack.asInstanceOf[Double] / stacks.asInstanceOf[Double]
			val y = math.cos(math.Pi * proportionStack)
			
			val stackRadius = math.sin(math.Pi * proportionStack)
			
			for(slice <- 0 until (slices + 1)){
				val vertDataInd = stack * (slices +1) + slice
				
				val proportionSlice = slice.asInstanceOf[Double] / slices.asInstanceOf[Double]
				val x = stackRadius * math.cos(twoPi * proportionSlice)
				val z = stackRadius * math.sin(twoPi * proportionSlice)
				
				vertexData(vertDataInd*3) = radius * x.asInstanceOf[Float];
				vertexData(vertDataInd*3+1) = radius * y.asInstanceOf[Float];
				vertexData(vertDataInd*3+2) = radius * z.asInstanceOf[Float];
				
				texCoordData(vertDataInd*2) = proportionSlice.asInstanceOf[Float];
				texCoordData(vertDataInd*2+1) = proportionStack.asInstanceOf[Float];
				
				normalData(vertDataInd*3) = x.asInstanceOf[Float];
				normalData(vertDataInd*3+1) = y.asInstanceOf[Float];
				normalData(vertDataInd*3+2) = z.asInstanceOf[Float];
				
				if(stack < stacks && slice < slices){
					val topLeft = stack * (slices+1) + slice
					val botLeft = topLeft + (slices+1)
					val botRight = topLeft + (slices+1) + 1
					val topRight = topLeft + 1
					
					val faceInd = 6 * (stack * slices + slice)
					faceData(faceInd) = topLeft
					faceData(faceInd+1) = botLeft
					faceData(faceInd+2) = botRight
					faceData(faceInd+3) = botRight
					faceData(faceInd+4) = topRight
					faceData(faceInd+5) = topLeft
				}
			}
		}
		
		val mesh = new MutableMesh(Triangle,faceCount,10)
		mesh.appendFaceData(faceData)
		mesh.appendVertData(Vertex, 3, vertexData)
		mesh.appendVertData(TexCoord1, 2, texCoordData)
		mesh.appendVertData(Normal, 3, normalData)
		mesh
	}
	
	/**
	 * Creates a square with vertices, normals, and 2D texture coordinates. 
	 * @param length the length along one side of the square
	 * @return A cube MutableMesh. 
	 */
	def Square(length:Float):MutableMesh = {
		val h = length / 2.0f
		val l = 1.0f
		val o = 0.0f
		val halfPi = 0.5f * math.Pi.asInstanceOf[Float];
		
		val fFace:Array[Int] = Array(0,1,2, 2,1,3)
		val fVerts:Array[Float] = Array(
				-h,-h,o, h,-h,o, -h,h,o, h,h,o)
		val fTexCoords:Array[Float] = Array(
				o,o, l,o, o,l, l,l)
		val fNormals:Array[Float] = Array(
				o,o,l, o,o,l, o,o,l, o,o,l)
		
		
		val mesh = new MutableMesh(Triangle,12,10)
		mesh.appendFaceData(fFace)
		mesh.appendVertData(Vertex, 3, fVerts)
		mesh.appendVertData(TexCoord1, 2, fTexCoords)
		mesh.appendVertData(Normal, 3, fNormals)
		mesh
	}
	
	/**
	 * Creates a cube with vertices, normals, and 2D texture coordinates. Each face of the sphere
	 * will display the entire 2D texture.
	 * @param length the length along one side of the cube
	 * @return A cube MutableMesh. 
	 */
	def Cube(length:Float):MutableMesh = {
		val h = length / 2.0f
		val l = 1.0f
		val o = 0.0f
		val halfPi = 0.5f * math.Pi.asInstanceOf[Float];
		
		val faceData:Array[Int] = new Array(36)			//6 sides * 2 triangles * 3 vertices
		val vertexData:Array[Float] = new Array(72) 	//6 sides * 4 vertices * 3 Floats per vertex
		val texCoordData:Array[Float] = new Array(48) 	//6 sides * 4 vertices * 2 Floats per vertex
		val normalData:Array[Float] = new Array(72) 	//6 sides * 4 vertices * 3 Floats per vertex
		
		val fFace:Array[Int] = Array(0,1,2, 2,1,3)
		val fVerts:Array[Float] = Array(
				-h,-h,h, -h,h,h, h,-h,h, h,h,h)
		val fTexCoords:Array[Float] = Array(
				o,o, o,l, l,o, l,l)
		val fNormals:Array[Float] = Array(
				o,o,l, o,o,l, o,o,l, o,o,l)
		
		//Rotate the base face around the y axis 4 times, and the x axis 2 times to form all 6 faces
		for(i <- 0 until 6){
		  val rot = 
		 	 if(i < 4){
		 	   val rads:Float = i.asInstanceOf[Float] * halfPi
		 	   RotateYMat(rads)
		 	 } 
		 	 else{
		 	   val rads:Float = (1 + (i%2)*2).asInstanceOf[Float] * halfPi
		 	   RotateXMat(rads)
		 	 }
		  
		  for(j <- 0 until 4){
		 	val fInd = j * 3
		 	val cInd = i * 4 * 3 + fInd
		 	
		 	val vertVec = VectorF(fVerts.slice(fInd, fInd+3))
		 	val newVert = rot * vertVec
		 	
		 	vertexData(cInd) = newVert(0) 
		 	vertexData(cInd + 1) = newVert(1) 
		 	vertexData(cInd + 2) = newVert(2)
		 	
		 	val normVec = VectorF(fNormals.slice(fInd, fInd+3))
		 	val newNorm = rot * normVec
		 	
		 	normalData(cInd) = newNorm(0)
		 	normalData(cInd + 1) = newNorm(1)
		 	normalData(cInd + 2) = newNorm(2)
		  }
		   
		  for(j <- 0 until 8){
		    val cInd = i * 8 + j
		    texCoordData(cInd) = fTexCoords(j) 
		  }
		   
		  for(j <- 0 until 6){
		 	val cInd = i * 6 + j
		 	faceData(cInd) = i * 4 + fFace(j)
		  }
	    }
		
		val mesh = new MutableMesh(Triangle,12,10)
		mesh.appendFaceData(faceData)
		mesh.appendVertData(Vertex, 3, vertexData)
		mesh.appendVertData(TexCoord1, 2, texCoordData)
		mesh.appendVertData(Normal, 3, normalData)
		mesh
	}
	
	
	/**
	 * Creates a torus with vertices, normals, and 2D texture coordinates
	 * @param circleParts the number of vertices that make up a complete circle if you were 
	 * to cut a slice out of the side of the torus
	 * @param circles the number of smaller circles that rotate around the center to form
	 * the shape of the torus
	 * @param innerRadius distance from the center to the inside of the torus. 
	 * @param outerRadius distance from the center to the outside of the torus.
	 * @return A MutableMesh approximating a torus. 
	 */
	def Torus(circleParts:Int, circles:Int, innerRadius:Float, outerRadius:Float):MutableMesh = {
		val faceCount = circleParts * circles * 2
		val vertCount = (circleParts + 1) * (circles + 1)
		
		val vertexData = new Array[Float](vertCount * 3);
		val texCoordData = new Array[Float](vertCount * 2);
		val normalData = new Array[Float](vertCount * 3);
		val faceData = new Array[Int](faceCount * 3)
		
		val circRadius = (outerRadius - innerRadius) / 2.0f 
		val circCenterRadius = innerRadius + circRadius
		
		val twoPi = 2.0 * math.Pi;
		
		val circVert = VectorF(0.0f,1.0f,0.0f)
		val trans = TranslateMat(0.0f,circCenterRadius,0.0f)
		val scale = ScaleMat(circRadius,circRadius,circRadius)
		
		for(circInd <- 0 until (circles + 1)){
			val proportionCircles = circInd.asInstanceOf[Float] / circles.asInstanceOf[Float]
			
			val rotZ = RotateZMat(twoPi.asInstanceOf[Float] * proportionCircles)
			val rotTrans = rotZ * trans
			
			for(part <- 0 until (circleParts + 1)) {
			  val proportionPart = part.asInstanceOf[Float] / circleParts.asInstanceOf[Float]
			  val rotX = RotateXMat(twoPi.asInstanceOf[Float] * proportionPart)
			  
			  val normVert = rotZ * rotX * circVert
			  val torusVert = rotTrans * rotX * scale * circVert

			  val vertDataInd = circInd * (circleParts +1) + part
			  
			  vertexData(vertDataInd*3) = torusVert(0);
			  vertexData(vertDataInd*3+1) = torusVert(1);
			  vertexData(vertDataInd*3+2) = torusVert(2);
				
			  texCoordData(vertDataInd*2) = proportionCircles.asInstanceOf[Float];
			  texCoordData(vertDataInd*2+1) = proportionPart.asInstanceOf[Float];
				
			  normalData(vertDataInd*3) = normVert(0);
			  normalData(vertDataInd*3+1) = normVert(1);
			  normalData(vertDataInd*3+2) = normVert(2);
			  
			  if(circInd < circles && part < circleParts){
				val topLeft = circInd * (circleParts+1) + part
				val botLeft = topLeft + (circleParts+1)
				val botRight = topLeft + (circleParts+1) + 1
				val topRight = topLeft + 1
				
				val faceInd = 6 * (circInd * circleParts + part)
				faceData(faceInd) = topLeft
				faceData(faceInd+1) = botLeft
				faceData(faceInd+2) = botRight
				faceData(faceInd+3) = botRight
				faceData(faceInd+4) = topRight
				faceData(faceInd+5) = topLeft
			  }
			}
		}
		
		val mesh = new MutableMesh(Triangle,faceCount,10)
		mesh.appendFaceData(faceData)
		mesh.appendVertData(Vertex, 3, vertexData)
		mesh.appendVertData(TexCoord1, 2, texCoordData)
		mesh.appendVertData(Normal, 3, normalData)
		mesh
	}
	
	def TriangleMesh(heightMap:SquareHeightMap, length:Float):MutableMesh = {
	  val faceArray = new Array[Int]((heightMap.length-1) * (heightMap.length-1) * 6)
	  val vertArray = new Array[Float](heightMap.length * heightMap.length * 3)
	  val texCoordArray = new Array[Float](heightMap.length * heightMap.length * 2)
      val normArray = new Array[Float](heightMap.length * heightMap.length * 3)
    
      val lengthDist = 1.0f / heightMap.length.asInstanceOf[Float]
	  val offset = -length * 0.5f
      val scale = length * lengthDist
	   
      var curIndex = 0;
      var curPoly = 0;
      for(x <- 0 until heightMap.length;
          y <- 0 until heightMap.length){
          val fX = offset + scale * x.asInstanceOf[Float]
          val fZ = offset + scale * y.asInstanceOf[Float]
          val fY = heightMap(x,y,true)
          
          vertArray(curIndex*3) = fX
          vertArray(curIndex*3+1) = fY
          vertArray(curIndex*3+2) = fZ
          
          texCoordArray(curIndex*2) = lengthDist * x
          texCoordArray(curIndex*2+1) = lengthDist * y
    
          val posX = VectorF(lengthDist, heightMap(x+1,y,true) - fY).normalize
          val negX = VectorF(-lengthDist, heightMap(x-1,y,true) - fY).normalize
          val posZ = VectorF(lengthDist, heightMap(x,y+1,true) - fY).normalize
          val negZ = VectorF(-lengthDist, heightMap(x,y-1,true) - fY).normalize
          
          val normX = VectorF(-posX(1),posX(0)) + VectorF(negX(1),-negX(0)).normalize
          val normZ = VectorF(-posZ(1),posZ(0)) + VectorF(negZ(1),-negZ(0)).normalize
          
          val norm = (VectorF(normX(0),normX(1),0.0f) + VectorF(0.0f,normZ(1),normZ(0))).normalize
          
          normArray(curIndex*3) = norm(0)
          normArray(curIndex*3+1) = norm(1)
          normArray(curIndex*3+2) = norm(2)
          
          curIndex = curIndex+1;
          
          if(x < (heightMap.length -1) && y < (heightMap.length -1)){
            val leftBot = x * heightMap.length + y
            val rightBot = (x+1) * heightMap.length + y
            val rightTop = (x+1) * heightMap.length + y + 1
            val leftTop = x * heightMap.length + y + 1
            faceArray(curPoly) = leftBot
            faceArray(curPoly+1) = rightBot
            faceArray(curPoly+2) = rightTop
            faceArray(curPoly+3) = rightTop
            faceArray(curPoly+4) = leftTop
            faceArray(curPoly+5) = leftBot
            curPoly+=6;
          }
      }
    
      val mesh = new MutableMesh(Triangle,faceArray.length / 3, 100)
      mesh.replaceFaceData(faceArray)
      mesh.replaceVertData(Vertex,3,vertArray)
      mesh.replaceVertData(Normal,3,normArray)
      mesh.replaceVertData(TexCoord1,2,texCoordArray)
      mesh
	}

}